package com.sxt;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import com.sxt.core.discovery.Discovery;
import com.sxt.core.discovery.impl.ZKDiscoveryImpl;
import com.sxt.model.Request;
import com.sxt.model.Response;
import com.sxt.service.AlgorithmService;

/**
 * 客户端，发送请求
 * 
 * @author Liushuangxi
 *
 */
public class Self {
	// 初始化随机数（负载均衡使用）
	private static Random RDM = new Random();
	
	public static void main(String[] args) {
		System.out.println("开始考试");
		System.out.println("有个题目不会，题目是1 + 1 = ?");
		System.out.println("但是室友会，我走之前还给他打招呼，让他在寝室等我短信！");
		System.out.println("给室友发送题目"); // 参数
		System.out.println("获取到室友的答案");// 结果
		// 我们知道接口,进行rpc调用
		AlgorithmService algFunctionServiceProxy = getProxyObject(AlgorithmService.class); // 让代理对象做rpc
		while(true) {
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			String answer = algFunctionServiceProxy.add(15,2);
			System.out.println("答案是："+answer);
		}
	}

	/**
	 * 实现远程调用
	 * @param class1
	 * @return
	 */
	public static <T> T getProxyObject(Class<?> inter) {
		@SuppressWarnings({ "unchecked", "unused" })
		T proxyInstance =(T) Proxy.newProxyInstance(Self.class.getClassLoader(), //类加载器
							   new Class<?>[] {inter},//接口的数组
							   new InvocationHandler() { //调用时，进入的方法
			@SuppressWarnings("resource")
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				System.out.println("将要执行invoke方法！");
				//得到方法名称
				String methodName = method.getName();
				//得到接口名称'
				String interfaceName = inter.getName();
				if("toString".equals(methodName)) {
					return interfaceName+"$Proxy" ;
				}
				if("hashCode".equals(methodName)) {
					return -1;
				}
				if("equeals".equals(methodName)) {
					return false ;
				}
				//构造请求对象
				Request request = new Request(interfaceName,methodName,args);
				
				// 准备发送
				Socket socket= null;
				OutputStream outputStream = null;
				InputStream inputStream =null;
				ObjectOutputStream objectOutputStream = null;
				ObjectInputStream objectInputStream =null;
				Response response = null;
				String server = null ;
				List<String> classmates  = new ArrayList<String>() ; //服务地址
				try {
					// 获取服务地址
					Discovery discovery = new ZKDiscoveryImpl();
					classmates.addAll(discovery.discovery("ADD"));
					
					// 从一个列表里面选一个服务出来：负载均衡问题
					server = loadbalance(classmates); // 1482
					System.out.println("服务列表为："+classmates);
					System.out.println("当前选择的服务是;:"+server);
					// 获取IP和port
					String[] strs = server.split(":");
					String ip = strs[0];
					Integer port = Integer.valueOf(strs[1]);
					// 发送题目
					socket = new Socket(ip, port);
					outputStream = socket.getOutputStream();
					objectOutputStream = new ObjectOutputStream(outputStream);
					objectOutputStream.writeObject(request);
					objectOutputStream.flush();
					
					// 接受答案
					inputStream = socket.getInputStream();
					objectInputStream = new ObjectInputStream(inputStream);
					response =(Response) objectInputStream.readObject();
				} catch (Exception e) {
					// 进入此方法，代表服务不可用(掉线，无法连接),自动获取服务列表，但是尝试再次连接，保证服务正常使用
					while(true) {
						Integer i = 0;
						// 服务列表不可用，需要剔除
						classmates.remove(server);
						// 负载均衡得到可用的服务地址
						server = loadbalance(classmates);
						// 重新发送题目，并得到新的服务
						response =  propagationOrContinue(server,i,request);
						if(response!=null) {
							return response;
						}
					}
					
				}finally {
					closes(objectInputStream,inputStream,objectOutputStream,outputStream,socket);
				}
				if(response==null||response.getError()!=null) {
					throw response.getError() ;
				}
				return response.getResult();
			}
			
			/**
			 * 传播特性，尝试服务再次连接
			 * @param server
			 *  服务名
			 * @param currentCount
			 *  尝试次数
			 * @param request
			 *  请求对象
			 * @return
			 */
			private Response propagationOrContinue(String server, Integer currentCount, Request request) {
				// 判断尝试次数
				if(currentCount>=3) {
					throw new RuntimeException("服务异常，尝试连接！");
				}
				currentCount++;
				// 判断服务是否存在
				if(server==null) {
					throw new RuntimeException("服务不存在！");
				}
				
				// 再次发送题目
				Socket socket = null ;
				OutputStream outputStream = null ;
				ObjectOutputStream objectOutputStream = null ;
				ObjectInputStream objectInputStream = null ;
				InputStream inputStream = null ;
				Response response = null ;
				try {
					String[] strs = server.split(":");
					socket = new Socket(strs[0], Integer.valueOf(strs[1]));
					outputStream = socket.getOutputStream();
					objectOutputStream = new ObjectOutputStream(outputStream);
					objectOutputStream.writeObject(request);
					objectOutputStream.flush();
					
					// 接受答案
					inputStream = socket.getInputStream();
					objectInputStream = new ObjectInputStream(inputStream);
					response =(Response) objectInputStream.readObject();
				} catch (Exception e) {
					System.out.println(e);
				}finally {
					closes(objectInputStream,inputStream,objectOutputStream,outputStream,socket);
				}
				return response;
			}
		} );
		return proxyInstance;
	}
	
	/**
	 * 选一个室友
	 * @param classmates
	 * @return
	 */
	private static String loadbalance(List<String> classmates) {
		// 涉及的是负载均衡
		int index = RDM.nextInt(classmates.size()); // 产生下标2 01
		return classmates.get(index);
	}
	
	/**
	 * 关闭流的方法
	 * @param stream
	 */
	public static void closes(Closeable ...stream) {
		for (Closeable closeable : stream) {
			if(null!=closeable) {
				try {
					closeable.close();
				} catch (IOException e) {
					e.printStackTrace();
				}finally {
					closeable = null;
				}
			}
		}
	}
}
